﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;

namespace pyTivoMetadataManager
{
    public class VideoMetadata
    {
        #region Props
        public string Filename;
        /// <summary>This is the time and date the file was recorded by a TiVo.</summary>
        public DateTime? RecordedTime;              //time
        /// <summary>This is the original air date of the episode.</summary>
        public DateTime? OriginalAirDate;           //originalAirDate
        /// <summary>This must be entered as yyyy. This is the original air date of the movie. Whether or not the info is correct doesn't matter. One should use movieYear for movies instead of originalAirDate (which is for television programs).</summary>
        public int? MovieYear;                      //movieYear
        /// <summary>Name of series (The Simpsons, Seinfeld, etc.). If left blank, series title information at the top of the Program screen will read Rec: <displayMajorNumber> <callsign> <HH:MM a/pm(record time adjusted for GMT)>. If omitted, series title information at the top of the Program screen will return the name of the containing folder on the server system. Seemingly endless number of characters can be entered, only so many will be shown on the Program screen, but all are shown on the Details screen. </summary>
        public string SeriesTitle;                  //seriesTitle
        /// <summary>Title of episode (don't need to include the name of the series in here. For example, if you have "Aqua Teen Hungerforce - Broodwich", just type Broodwich. TiVo will put it in the right place with appropriated quotes. Leaving this field blank will cause everything in that folder to disappear. Omitting this field will cause the video filename to be returned as the episode’s title, no other ill effects. Seeminly endless number of characters can be entered, only so many will be shown on the Program screen, but all are shown on the Details screen. </summary>
        public string Title;                        //title
        /// <summary>Same as the "title" field. Changing this to something other than the title field doesn't seem to change anything. Can be left blank or omitted with no ill effects. Probably not worth the effort of putting in the file. </summary>
        public string EpisodeTitle;                 //episodeTitle
        /// <summary>The synopsis of the video . Seemingly endless number of characters can be entered, only so many will be shown on the Program screen, but all are shown on the Details screen. </summary>
        public string Description;                  //description
        /// <summary>If true, the year from originalAirDate will be shown in parentheses after the episode’s title and before the description on the Program screen. If false, the year will not appear. Does not affect grouping based on seriesId.</summary>
        public bool IsEpisode = false;              //isEpisode
        /// <summary>
        /// Usually starts with "SH" and followed by 6-8 digits. TiVo uses this to allow for grouping once a program is transferred to a TiVo, provided the TiVo is set to grouping in the NPL (Now Playing List). Leaving this field blank or omitting doesn’t seem to cause any problems, except when it comes time to group when transferring to TiVo. Will not group without a series ID.
        /// * SH is for general TV shows.
        /// * MV is for movies (e.g. Amazon Unbox).
        /// * SP is for sports.
        /// * TS is for Tivocast content. 
        /// </summary>
        public string SeriesId;                     //seriesId
        /// <summary>This only shows up if you go into the Details from the Program screen. This seems to disappear once the video is transferred to TiVo.</summary>
        public int? EpisodeNumber;                   //episodeNumber
        /// <summary>This is the channel the episode was recorded from. Value will not be displayed on Program screen unless callsign value is entered as well. No more then 5 digits will be displayed. If more than 5 digits are entered, a seemingly random five-digit number will be displayed in its place. </summary>
        public int? ChannelNumber;                   //displayMajorNumber
        /// <summary>This doesn’t seem to do anything.</summary>
        public int? SubChannelNumber;                //displayMinorNumber
        /// <summary>This is the call sign of the channel the episode was recorded from. Only 7 characters will be displayed. However, it shares space with the displayMajorNumber field, and displayMajorNumber gets first priority. If displayMajorNumber has all 5 of its allowed characters entered, only 3 of the callsign digits are displayed, followed by an ellipsis. If only 4 of displayMajorNumber’s allowed characters are entered, 4 of callsign’s digits are displayed. When only 3 of displayMajor’s allowed characters are entered, all 7 of callsign’s digits will be displayed.</summary>
        public string ChannelName;                  //callsign
        /// <summary>This tells the TiVo to display various combinations things in parentheses at the end of the description.</summary>
        /// <seealso cref="pyTivoMetadataManager.ShowingBits"/>
        public ShowingBits? ShowingBits;             //showingBits
        /// <summary>As far as I can tell, these don’t do anything.</summary>
        public DateTime? StartTime;                  //startTime
        /// <summary>As far as I can tell, these don’t do anything.</summary>
        public DateTime? StopTime;                   //stopTime
        /// <summary>This is shown on both the Program screen and the Details screen. It uses the standard TV rating system of: TV-Y7, TV-Y, TV-G, TV-PG, TV-14, TV-MA and TV-NR.</summary>
        /// <seealso cref="pyTivoMetadataManager.TvRatings"/>
        public string TvRating;                     //tvRating
        /// <summary>This is shown on both the Program screen and the Details screen.</summary>
        /// <seealso cref="pyTivoMetadataManager.StarRatings"/>
        public string StarRating;                   //starRating
        /// <summary>This is shown on both the Program screen and the Details screen.</summary>
        /// <seealso cref="pyTivoMetadataManager.MpaaRatings"/>
        public string MpaaRating;                   //mpaaRating
        /// <summary>This is shown on the Details screen.</summary>
        /// <seealso cref="pyTivoMetadataManager.ColorCodes"/>
        public string ColorCode;                    //colorCode
        /// <summary>Only the first two entries will show up on the Program screen, but all the rest will show up on the Details screen from the Program screen.</summary>
        /// <remarks>These values will show up when viewing files on your server, but once transferred to TiVo, they seem to disappear.</remarks>
        public List<string> Genres;                 //vProgramGenre
        /// <summary>This doesn't seem to show up anywhere. I suspect that TiVo uses these values when deciding on what programs to recommend or other internal functions.</summary>
        public List<string> SeriesGenres;           //vSeriesGenre
        /// <summary>Actors show up on the Program screen, All the rest of these show up only on the Details screen.</summary>
        /// <remarks>    While one can put the name in common order, the internal TiVo custom is to enter as:
        ///     * lastname|firstname
        ///     * lastname modifier|firstname (e.g. "Begley Jr.|Ed")
        ///     * lastname|firstname middlename (e.g. "Brooks|James L.", "Olmos|Edward James")
        ///     * multi lastname|firstname (e.g. "de Ravin|Emilie")
        ///     * lastname|initial firstname (e.g. "Fitzgerald|L. Scott", "Gainey|M. C.")
        ///     * onename| (e.g. "Coolio|")
        ///     * title|firstname (e.g. "the Entertainer|Cedric")
        ///     * band name| 
        /// </remarks>
        public List<Person> People;                 //vActor, vGuestStar, vDirector, vExecProducer, vProducer, vWriter, vHost, vChoreographer
        /// <summary></summary>
        public int Advisory = 1;           //vAdvisory
        /// <summary></summary>
        public int RecordingQuality = 75;  //recordingQuality
        #endregion

        public VideoMetadata()
        {
            Genres = new List<string>();
            SeriesGenres = new List<string>();
            People = new List<Person>();
        }

        public void LoadFromMetadataFile(string filename)
        {
            Filename = filename;

            Genres = new List<string>();
            SeriesGenres = new List<string>();
            People = new List<Person>();

            // Open metadata
            string[] metadata = File.ReadAllLines(filename);

            string key = null, value = null;
            foreach (string metaline in metadata)
            {
                if (!metaline.Trim().Equals("", StringComparison.InvariantCultureIgnoreCase))
                {
                    int idxColon = metaline.IndexOf(":");
                    if (idxColon >= 0)
                    {
                        if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value))
                        {
                            LoadProperty(key, value);
                            key = value = null;
                        }
                        key = metaline.Substring(0, idxColon).Trim();
                        value = metaline.Substring(idxColon + 1).TrimStart();
                    }
                    else
                    {
                        if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value))
                        {
                            value += "\n" + metaline;
                        }
                    }
                }
            }
            if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value))
            {
                LoadProperty(key, value);
            }
        }

        public void SaveToMetadataFile(string filename)
        {
            using (StreamWriter sw = File.CreateText(filename))
            {
                WriteProperty(sw, "time", RecordedTime); //
                WriteProperty(sw, "originalAirDate", OriginalAirDate);//
                WriteProperty(sw, "movieYear", MovieYear, "0000");//
                WriteProperty(sw, "seriesTitle", SeriesTitle);//
                WriteProperty(sw, "title", Title);//
                WriteProperty(sw, "episodeTitle", EpisodeTitle);//
                WriteProperty(sw, "description", Description);//
                WriteProperty(sw, "isEpisode", IsEpisode);//
                WriteProperty(sw, "seriesId", SeriesId);
                WriteProperty(sw, "episodeNumber", EpisodeNumber);
                WriteProperty(sw, "displayMajorNumber", ChannelNumber);//
                WriteProperty(sw, "callsign", ChannelName);//
                WriteProperty(sw, "showingBits", (long)ShowingBits);
                WriteProperty(sw, "displayMinorNumber", SubChannelNumber);//
                WriteProperty(sw, "startTime", StartTime);
                WriteProperty(sw, "stopTime", StopTime);
                WriteProperty(sw, "tvRating", TvRating);
                WriteProperty(sw, "starRating", StarRating);
                WriteProperty(sw, "mpaaRating", MpaaRating);
                WriteProperty(sw, "colorCode", ColorCode);
                foreach (string genre in Genres)
                {
                    WriteProperty(sw, "vProgramGenre", genre); //
                }
                foreach (string seriesGenre in SeriesGenres)
                {
                    WriteProperty(sw, "vSeriesGenre", seriesGenre);
                }
                foreach (Person person in People)
                {
                    WriteProperty(sw, person);
                }
                WriteProperty(sw, "recordingQuality", RecordingQuality);
                WriteProperty(sw, "vAdvisory", Advisory);

            }
        }

        private string _propertyFormat = "{0}: {1}";
        private void WriteProperty(StreamWriter sw, string property, string value)
        {
            if (!string.IsNullOrEmpty(value)) sw.WriteLine(_propertyFormat, property, value);
        }
        private void WriteProperty(StreamWriter sw, string property, DateTime? value)
        {
            if (value!=null) sw.WriteLine(_propertyFormat, property, ((DateTime)value).ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"));
        }
        private void WriteProperty(StreamWriter sw, string property, int? value)
        {
            if (value != null) sw.WriteLine(_propertyFormat, property, ((int)value).ToString());
        }
        private void WriteProperty(StreamWriter sw, string property, int? value, string format)
        {
            if (value != null) sw.WriteLine(_propertyFormat, property, ((int)value).ToString(format));
        }
        private void WriteProperty(StreamWriter sw, string property, bool? value)
        {
            if (value != null) sw.WriteLine(_propertyFormat, property, ((bool)value).ToString());
        }
        private void WriteProperty(StreamWriter sw, string property, long? value)
        {
            if (value != null) sw.WriteLine(_propertyFormat, property, ((long)value).ToString());
        }
        private void WriteProperty(StreamWriter sw, string property, long? value, string format)
        {
            if (value != null) sw.WriteLine(_propertyFormat, property, ((long)value).ToString(format));
        }
        private void WriteProperty(StreamWriter sw, Person value)
        {
            if (value != null)
            {
                string property = null;
                switch (value.Type)
                {
                    case PersonType.Actor:
                        property = "vActor";
                        break;
                    case PersonType.Choreographer:
                        property = "vChoreographer";
                        break;
                    case PersonType.Director:
                        property = "vDirector";
                        break;
                    case PersonType.ExecutiveProducer:
                        property = "vExecProducer";
                        break;
                    case PersonType.GuestStar:
                        property = "vGuestStar";
                        break;
                    case PersonType.Host:
                        property = "vHost";
                        break;
                    case PersonType.Producer:
                        property = "vProducer";
                        break;
                    case PersonType.Writer:
                        property = "vWriter";
                        break;
                }
                sw.WriteLine(_propertyFormat, property, value.Name);
            }
        }

        private void LoadProperty(string key, string value)
        {
            if ("time".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                RecordedTime = DateTime.Parse(value);
            }
            else if ("originalAirDate".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                OriginalAirDate = DateTime.Parse(value);
            }
            else if ("movieYear".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                MovieYear = int.Parse(value);
            }
            else if ("seriesTitle".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                SeriesTitle = value.Trim();
            }
            else if ("title".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                Title = value.Trim();
            }
            else if ("episodeTitle".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                EpisodeTitle = value.Trim();
            }
            else if ("description".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                Description = value;
            }
            else if ("isEpisode".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                IsEpisode = bool.Parse(value);
            }
            else if ("seriesId".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                SeriesId = value.Trim();
            }
            else if ("episodeNumber".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                EpisodeNumber = int.Parse(value);
            }
            else if ("displayMajorNumber".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                ChannelNumber = int.Parse(value);
            }
            else if ("callsign".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                ChannelName = value.Trim();
            }
            else if ("showingBits".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                ShowingBits = (ShowingBits)long.Parse(value);
            }
            else if ("displayMinorNumber".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                SubChannelNumber = int.Parse(value);
            }
            else if ("startTime".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                StartTime = DateTime.Parse(value);
            }
            else if ("stopTime".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                StopTime = DateTime.Parse(value);
            }
            else if ("tvRating".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                TvRating = value.Trim();
            }
            else if ("starRating".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                StarRating = value.Trim();
            }
            else if ("mpaaRating".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                MpaaRating = value.Trim();
            }
            else if ("colorCode".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                ColorCode = value.Trim();
            }
            else if ("vProgramGenre".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                Genres.Add(value.Trim());
            }
            else if ("vSeriesGenre".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                SeriesGenres.Add(value.Trim());
            }
            else if ("vActor".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                People.Add(new Person { Name = value.Trim(), Type = PersonType.Actor });
            }
            else if ("vGuestStar".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                People.Add(new Person { Name = value.Trim(), Type = PersonType.GuestStar });
            }
            else if ("vDirector".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                People.Add(new Person { Name = value.Trim(), Type = PersonType.Director });
            }
            else if ("vExecProducer".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                People.Add(new Person { Name = value.Trim(), Type = PersonType.ExecutiveProducer });
            }
            else if ("vProducer".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                People.Add(new Person { Name = value.Trim(), Type = PersonType.Producer });
            }
            else if ("vWriter".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                People.Add(new Person { Name = value.Trim(), Type = PersonType.Writer });
            }
            else if ("vHost".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                People.Add(new Person { Name = value.Trim(), Type = PersonType.Host });
            }
            else if ("vChoreographer".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                People.Add(new Person { Name = value.Trim(), Type = PersonType.Choreographer });
            }
            else if ("recordingQuality".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                RecordingQuality = int.Parse(value);
            }
            else if ("vAdvisory".Equals(key, StringComparison.InvariantCultureIgnoreCase))
            {
                Advisory = int.Parse(value);
            }
            else
            {
                Debug.WriteLine("Unknown property: \"{0}\"", key);
            }
        }
    }

    public class Person
    {
        public string Name;
        public PersonType Type;
    }

    public enum PersonType
    {
        Actor,
        GuestStar,
        Director,
        ExecutiveProducer,
        Producer,
        Writer,
        Host,
        Choreographer
    }

    [Flags]
    public enum ShowingBits : long
    {
        CC = 1,
        Stereo = 2,
        Subtitled = 4,
        InProgress = 8,
        Classroom = 16,
        SAP = 32,
        Blackout = 64,
        Intercast = 128,
        ThreeD = 256,
        Repeat = 512,
        Letterbox = 1024,
        HD = 4096,
        //Ratings
        RatedSex = 65536,
        RatedViolence = 131072,
        RatedLanguage = 262144,
        RatedDialog = 524288,
        RatedFantasyViolence = 1048576,
    }

    public static class MpaaRatings
    {
        [DisplayName("G")]
        public const string G = "G1";
        [DisplayName("PG")]
        public const string PG = "P2";
        [DisplayName("PG-13")]
        public const string PG13 = "P3";
        [DisplayName("R")]
        public const string R = "R4";
        [DisplayName("X")]
        public const string X = "X5";
        [DisplayName("NC-17")]
        public const string NC17 = "N6";
        [DisplayName("NR")]
        public const string NR = "N8";
    }

    public static class TvRatings
    {
        [DisplayName("None")]
        public const string None = "x0";
        [DisplayName("TV-Y7")]
        public const string tv_y7 = "x1";
        [DisplayName("TV-Y")]
        public const string tv_y = "x2";
        [DisplayName("TV-G")]
        public const string tv_g = "x3";
        [DisplayName("TV-PG")]
        public const string tv_pg = "x4";
        [DisplayName("TV-14")]
        public const string tv_14 = "x5";
        [DisplayName("TV-MA")]
        public const string tv_ma = "x6";
        [DisplayName("TV-NR")]
        public const string tv_nr = "x7";
    }

    public static class StarRatings
    {
        [DisplayName("1")]
        public const string One = "x1";
        [DisplayName("1.5")]
        public const string OneAndAHalf = "x2";
        [DisplayName("2")]
        public const string Two = "x3";
        [DisplayName("2.5")]
        public const string TwoAndAHalf = "x4";
        [DisplayName("3")]
        public const string Three = "x5";
        [DisplayName("3.5")]
        public const string ThreeAndAHalf = "x6";
        [DisplayName("4")]
        public const string Four = "x7";
    }

    public static class ColorCodes
    {
        [DisplayName("Black & White")]
        public const string BW = "x1";
        [DisplayName("Color And B&W")]
        public const string ColorAndBW = "x2";
        [DisplayName("Colorized")]
        public const string Colorized = "x3";
        [DisplayName("Color")]
        public const string ColorSeries = "x4";
    }
}